import Mn from 'backbone.marionette';
import Validation from 'backbone-validation';
import {
    serializeForm
} from '../../tools.js';
import BaseFormBehavior from './BaseFormBehavior.js';
import Radio from 'backbone.radio';

var FormBehavior = Mn.Behavior.extend({
    /*
    Behavior used to manage forms machinery

    Expects the attached View to have :

    a model
    a destCollection
    a form tag
    a type=submit button
    a type=reset button


    * By default models are synced on the server (added/edited) POST/PUT request

    You can affect this behavior :

        bb_sync

            if your view has an attribute bb_sync set to false it will become a simple frontend-only add/edit form

    * This form is an add form if the model has no id.

    * Only datas provided by the current form are validated and synced (not the
    whole model)

    You can affect this behavior by setting your view :

        partial

            If your view wants onlye part of the model to be synced


    View Events

    The following Events are triggered on the view

    cancel:form
    success:sync

    Machinery Event

    The following events can be fired from the view

    reset:model                  : Call the rollback method on the model

    Inherited from BaseFormBehavior

    data:modified  (attr, value) : Synchronize a field's datas with the model
    data:persist   (attr, value) : Synchronize the passed datas
    data:invalid   (model, errors, datas) : Datas are invalid when submitting


    Example :

    Sync the model's datas on field edition

        const MyView = Mn.View.extend({
            behaviors: [FormBehavior],
            bb_sync: false,
            childViewTriggers: {
                'finish': 'data:persist'
            }
        });

    Keep model in sync with the form during edition

        const MyView = Mn.View.extend({
            behaviors: [FormBehavior],
            bb_sync: false,
            childViewTriggers: {
                'finish': 'data:modified'
            }
        });
    */
    behaviors: [BaseFormBehavior],
    ui: {
        form: "form",
        reset: "button[type=reset]"
    },
    events: {
        'submit @ui.form': 'onSubmitForm',
        'click @ui.reset': 'onCancelClick',
    },
    defaults: {
        errorMessage: "Une erreur est survenue"
    },
    // Options retrieval
    getSyncOption() {
        let bb_sync = true;
        if (this.view.bb_sync === false) {
            bb_sync = false;
        }
        return bb_sync;
    },
    getPartialOption() {
        /* Return true if the model is only partially edited */
        let partial = true;
        if (this.view.partial === false) {
            partial = false;
        }
        return partial;
    },
    serializeForm() {
        return serializeForm(this.getUI('form'));
    },
    onSyncError(datas, status, result) {
        var channel = Radio.channel("message");
        channel.trigger('notify:success', result);
        Validation.unbind(this.view);
    },
    onSyncSuccess(datas, status, result) {
        console.log("FormBehavior.onSuccessSync");
        var channel = Radio.channel("message");
        channel.trigger('notify:success', result);
        Validation.unbind(this.view);
        let eventName = "success:sync";
        console.log("Trigger %s from FormBehavior", eventName);
        this.view.triggerMethod(eventName);
    },
    syncServer(datas, bound, source_event_type) {
        console.log("FormBehavior syncServer");
        var bound = bound || false;
        if (this.getPartialOption()) {
            datas = datas || this.view.model.toJSON();
        } else {
            datas = this.view.model.toJSON();
        }

        if (!bound) {
            Validation.bind(this.view, {
                attributes(view) {
                    return _.keys(datas)
                }
            });
        }

        if (this.view.model.isValid(_.keys(datas))) {
            let request;
            const view = this.view;
            view.triggerMethod('before:sync');
            if (!this.view.model.get('id') && !this.view.forceEditForm) {
                request = this.addSubmit(datas);
            } else {
                request = this.editSubmit(datas);
            }

            request.then(function (datas, status, result) {
                view.triggerMethod('form:' + source_event_type);
                return datas, status, result;
            })
            return request.done(
                this.onSyncSuccess.bind(this)
            ).fail(
                this.onSyncError.bind(this)
            );
        } else {
            console.log("Datas are invalid");
            const errors = this.view.model.validate(datas);
            console.log(errors);
            this.view.triggerMethod('data:invalid', this.view.model, errors, datas);
            return false;
        }
    },
    addSubmit(datas) {
        /*
         *
         * Since collection.create doesn't return a jquery promise, we need to
         * re-implement the destcollection create stuff and return the expected
         * promise
         *
         * See sources : (Collection.create)
         * http://backbonejs.org/docs/backbone.html
         *
         */
        console.log("FormBehavior.addSubmit");
        var destCollection = this.view.getOption('destCollection');
        console.log(destCollection)
        let model = destCollection._prepareModel(datas);
        // We replace the current model to get in sync
        this.view.model = model;
        let request;
        if (this.getSyncOption()) {
            request = model.save(
                null, {
                    wait: true,
                    sort: true
                }
            );
        } else {
            request = $.Deferred();
            request.resolve(model, null, null);
        }

        request = request.done(
            function (model, resp, callbackOpts) {
                console.log("Adding to collection");
                destCollection.add(model, callbackOpts);
                destCollection.trigger('saved');
                return model, 'success', resp;
            }
        ).fail(this.onSyncError.bind(this));
        return request;
    },
    editSubmit(datas) {
        console.log("FormBehavior.editSubmit");
        let request;
        if (this.getSyncOption()) {
            request = this.view.model.save(datas, {
                wait: true,
                patch: true
            });
        } else {
            request = $.Deferred();
            request.resolve(this.view.model);
        }
        return request;
    },
    onSubmitForm(event) {
        console.log("FormBehavior.onSubmitForm");
        event.preventDefault();
        let datas = this.syncFormWithModel();
        const request = this.syncServer(datas, false, 'submitted');

        if (request) {
            let view = this.view;
            request.then(function () {
                console.log("Trigger 'form:submitted' from FormBehavior");
                view.triggerMethod('form:submitted');
            });
        }
    },
    onDataPersisted(datas) {
        console.log("FormBehavior.onDataPersisted");
        this.syncServer(datas, true, 'persisted');
    },
    onCancelClick() {
        console.log("FormBehavior.onCancelClick");
        console.log("Trigger reset:model from FormBehavior");
        this.view.triggerMethod('reset:model');
        let eventName = 'cancel:form';
        console.log("Trigger %s from FormBehavior", eventName);
        this.view.triggerMethod(eventName);
    },
    onResetModel() {
        console.log("FormBehavior.onResetModel");
        this.view.model.rollback(this.getSyncOption());
    },
    syncFormWithModel() {
        /* Set form datas on the model and fires datas validation */
        var datas = this.serializeForm();
        console.log("FormBehavior.syncFormWithModel");
        if (this.view.afterSerializeForm) {
            datas = this.view.afterSerializeForm(datas);
        }
        let model = this.view.model.set(datas, {
            validate: true,
            silent: true
        });
        return datas;
    }
});

export default FormBehavior;
